
#include <iostream>
#include <fstream>

#include <cstdio>
#include <io.h>
#include <string>
#include <map>

#include <pthread.h>

#include <winsock2.h>
//#pragma comment(lib, "libwsock32")

#include "Connection.h"
#include "Client.h"
#include "CONSTVAL.h"

#include "glue.h"

#include "md5.h"


void splitFileName(const std::string &filepath, FileInfo *pFile);
int getPath(char *pL, int size);

int getPath(char *pL, int size)
{
    std::string path(getenv("HOMEDRIVE"));
    path += getenv("HOMEPATH");
    memcpy(pL, path.c_str(), size);
    return 0;
}

void
splitFileName(const std::string &filepath, FileInfo *pFile)
{
    size_t found;
    found = filepath.find_last_of("/\\");
    strncpy(pFile->pathname, filepath.substr(0, found).c_str(),
            sizeof(pFile->pathname));
    strncpy(pFile->filename, filepath.substr(found + 1).c_str(),
            sizeof(pFile->filename));
    return ;
}

Connection::Connection():
    m_recvfd(0),
    m_sendfd(0),
    m_conaddr( {0}),
m_recvPid( {0}),
m_sendfilePid( {0})
{
    Client::getSingleClient()->addConnection(this);
    return ;
}


Connection::Connection(const SOCKADDR_IN &newAddr, int newRecvfd):
    m_recvfd(newRecvfd),
    m_sendfd(0),
    m_conaddr(newAddr),
    m_recvPid( {0}),
m_sendfilePid( {0})
{
//    pthread_mutex_init(&m_mutex, NULL);
//    pthread_mutex_lock(&m_mutex);

    Client::getSingleClient()->addConnection(this);
    if (newRecvfd)
    {
        m_sendfd = newRecvfd;

    }
    else
    {
        startCon();
        m_recvfd = m_sendfd;
    }

    if (m_recvfd)
    {
        pthread_create(&m_recvPid, NULL, this->thread_recv, this);
    }

    std::cerr << "new a object" << this << std::endl;

//    pthread_mutex_unlock(&m_mutex);

    return ;
}

Connection::~Connection()
{
    closeCon();
    return ;
}

int Connection::getRecvfd()
{
    return m_recvfd;
}


bool Connection::compare(const Connection &cpCon)
{
    if (cpCon.m_conaddr.sin_addr.s_addr == this->m_conaddr.sin_addr.s_addr)
    {
        return true;
    }
    else
    {
        return false;
    }
}

bool Connection::isExist(const SOCKADDR_IN &cpAddr)
{
    if (cpAddr.sin_addr.s_addr == this->m_conaddr.sin_addr.s_addr &&
            cpAddr.sin_port == this->m_conaddr.sin_port)
    {
        return true;
    }
    else
    {
        return false;
    }

}

SOCKADDR_IN
Connection::getSelfAddr()
{
    return this->m_conaddr;
}

int Connection::startCon()
{

    m_sendfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    int nResult = connect(m_sendfd, (SOCKADDR *)&m_conaddr, sizeof(m_conaddr));
    std::cerr << m_conaddr.sin_port << " : " << ntohs(m_conaddr.sin_port) << " : " << inet_ntoa(m_conaddr.sin_addr) << std::endl;
    if (nResult == SOCKET_ERROR)
    {
        perror("connection error");
        std::cerr << "error num : " << WSAGetLastError() << std::endl;
        closeCon();
        return -1;
    }

    char buf[10];
    itoa(Client::getPort(), buf, 10);
    send(m_sendfd, (char *)buf, sizeof(buf), 0);
    m_recvfd = m_sendfd;


    return 0;
}

int
Connection::waitForRecv(Connection &pC)
{
    char buf[MAXBUFSIZE];
    ConInfo recvConInfo;
    FileCon *pFileCon;
    FileCon fCon;
    FileInfo *pFileInfo;

    int nResult;

    std::string str_filepath;
    int nProgress;

    bool md5_check;

    std::map<int, FileStatus>::iterator mapIt;
    FileStatus status;
    FileStatus *pStatus;
    memset(&recvConInfo, 0, sizeof(recvConInfo));
    std::fstream fout;
    while (true)
    {


        std::cerr << &pC << " wait for recv"
                  << pC.m_recvfd << " " << pC.m_sendfd <<std::endl;
        nResult = recv(pC.m_recvfd, (char *) &recvConInfo, sizeof(recvConInfo), 0);

        if (nResult <= 0)
        {
            std::cerr << &pC << "  Recv error: " << nResult << std::endl;

            pC.closeCon();

            break;
        }

#ifdef SHOWFILESEND
        {
            std::cerr << "Recv:" << nResult << std::endl;
            std::cerr << (int) recvConInfo.type << std::endl;
        }
#endif

        switch (recvConInfo.type)
        {
        case CI_MSG:
            std::cerr << "You recieved a message from : "
                      << pC.m_conaddr.sin_addr.s_addr << std::endl;
            //std::cerr << (int) recvConInfo.type << std::endl;
            std::cerr << recvConInfo.content << std::endl;
            std::cerr << pC.m_conaddr.sin_port << " : " << ntohs(pC.m_conaddr.sin_port) << " : " << inet_ntoa(pC.m_conaddr.sin_addr) << std::endl;
            std::cerr << pC.m_conaddr.sin_port << std::endl;
            recv_message(pC.m_conaddr, recvConInfo.content);

            break;
        case CI_FILECONTENT:
            // recvFile(recvConInfo);
            pC.writeFileContent(recvConInfo, str_filepath, nProgress);
            recv_file(str_filepath, nProgress);
            break;
        case CI_FILENEW:
            pC.addFileToRecv(recvConInfo, &pFileInfo);
            recv_file_init(pC.m_conaddr, *pFileInfo);
            break;
        case CI_FILECLOSE:
            pC.closeFile(recvConInfo, md5_check);
            std::cerr << "md5check : " << md5_check << std::endl;
            break;
        case CI_FILESTARTSEND:
            pFileCon = (FileCon *) recvConInfo.content;

            mapIt = pC.m_FilesSend.find(pFileCon->key);
            if (mapIt == pC.m_FilesSend.end())
            {
                std::cerr << "map not found, confirm failed" << std::endl;
                std::cerr << "keyL" << pFileCon->key << std::endl;
                std::cerr << "==================all map===========" << pFileCon->key << std::endl;
                for (mapIt=pC.m_FilesSend.begin(); mapIt!=pC.m_FilesSend.end();mapIt++)
                {
                    std::cerr << "key:" << mapIt->first
                    << "status:"<< mapIt->second.status
                    << "name:" << mapIt->second.fileInfo.filename<< std::endl;

                }
                std::cerr << pC.m_FilesSend.size() << std::endl;
            }
            pStatus = &(mapIt->second);

            pStatus->status = FS_FILE_SENDING;
            std::cerr << pStatus->fileInfo.filename << std::endl;
            std::cerr << "recv file confirm" << std::endl;
            break;
        default:
            break;
        }

        continue;

    }
    return 0;
}

int
Connection::closeFile(ConInfo & recvCon, bool &md5_check)
{

    FileCon *pFileCon = (FileCon *) recvCon.content;

    std::map<int, FileStatus>::iterator mapIt;
    mapIt = this->m_FilesRecv.find(pFileCon->key);
    FileStatus *pStatus = &mapIt->second;
    fclose(pStatus->pFILE);
    std::string filepath(pStatus->fileInfo.pathname);
    filepath += "\\";
    filepath += pStatus->fileInfo.filename;

    fclose(pStatus->pFILE);
    std::ifstream stream(filepath.c_str(), std::ios::binary);
    md5 fileMd5(stream);
    std::string str_md5 = fileMd5.md5_get_hash();

    std::cerr << "recv md5 check :" << str_md5 << endl;
    stream.close();

    if (str_md5.compare(pStatus->fileInfo.md5) == 0)
    {
        md5_check = true;
        std::cerr << "\tcheck state : equal" << endl;
    }
    else
    {
        md5_check = false;
        std::cerr << "\tcheck state : not equal" << std::endl;
        std::cerr << "\t" << str_md5 << std::endl;
        std::cerr << "\t" << pStatus->fileInfo.md5 << std::endl;
    }

    this->m_FilesRecv.erase(mapIt);

    std::cerr << "File close successful" << std::endl;

    FileCon fCon;
    fCon.key = pFileCon->key;

    ConInfo con;
    con.type = CI_FILECLOSE;

    return 0;
}

int
Connection::writeFileContent(ConInfo & fileCon, std::string &filepath, int &nProgress)
{
    std::map<int, FileStatus>::iterator mapIt;
    FileCon *pCon;
    pCon = (FileCon *) fileCon.content;

#ifdef FILESHOWSEND
    std::cerr << "Block size: " << pCon->len << std::endl;
#endif

    mapIt = m_FilesRecv.find(pCon->key);
    FileStatus *pStatus = &mapIt->second;

    if (pStatus->status == FS_FILE_WAITFORSEND)
    {
        std::cerr << "filesize" << pStatus->fileInfo.filesize << std::endl;
        fwrite(pCon->content, sizeof(pCon->content[0]), pCon->len, pStatus->pFILE);
        fflush(pStatus->pFILE);
        pStatus->curSize += pCon->len;
        if (pStatus->curSize == pStatus->fileInfo.filesize)
        {
            nProgress = 100;
        }
        else
        {
            nProgress = (int) ((double)pStatus->curSize / pStatus->fileInfo.filesize * 100);
        }
        filepath = pStatus->fileInfo.pathname;
        filepath += "\\";
        filepath += pStatus->fileInfo.filename;
        //#ifdef FILESHOWSEND
        std::cerr << "Block size for recv : \n\t"
                  << pStatus->curSize << " : " << pStatus->fileInfo.filesize << std::endl;
        //#endif
    }

    return 0;
}

int
Connection::closeCon()
{
    if (m_recvPid.p)
    {
        pthread_cancel(m_recvPid);
        m_recvPid = {0};
    }

    if (m_sendfilePid.p)
    {
        pthread_cancel(m_sendfilePid);
        m_sendfilePid = {0};
    }

    FileStatus *pStatus;
    std::map<int, FileStatus>::iterator mapIt;
    for (mapIt = m_FilesRecv.begin(); mapIt != m_FilesRecv.end();)
    {
        pStatus = &mapIt->second;
        recv_file(std::string(pStatus->fileInfo.pathname)
                  + "\\" + pStatus->fileInfo.filename,
                  -1);
        fclose(pStatus->pFILE);
        m_FilesRecv.erase(mapIt++);
    }

    for (mapIt = m_FilesSend.begin(); mapIt != m_FilesSend.end();)
    {
        pStatus = &mapIt->second;
        send_file(std::string(pStatus->fileInfo.pathname)
                  + "\\" + pStatus->fileInfo.filename,
                  -1);
        fclose(pStatus->pFILE);
        m_FilesSend.erase(mapIt++);
    }

    closesocket(m_recvfd);

    m_recvfd = m_sendfd = 0;

    return 0;
}



int
Connection::sendMsg(const std::string msg)
{

    if (this->m_sendfd == 0)
    {

        startCon();
    }
    std::cerr << m_conaddr.sin_port << " : " << ntohs(m_conaddr.sin_port) << " : " << inet_ntoa(m_conaddr.sin_addr) << std::endl;
    if (this->m_sendfd == 0)
    {
        perror("sendfd is not ready");
        return -1;
    }

    ConInfo msgCon;
    msgCon.type = CI_MSG;
    strcpy(msgCon.content, msg.c_str());

    int nResult;

    nResult = send(m_sendfd, (char *) &msgCon, (int) sizeof(msgCon), 0);
    if (nResult <= 0)
    {
        perror("send msg error");
    }

    return nResult;
}

int
Connection::testConnect()
{
    while (true)
    {
        if (0 == this->m_sendfd)
        {
            std::cerr << "SendFd is not ready." << std::endl;
            return -1;
        }
        std::cin.sync();
        std::cerr << "Type the request" << std::endl;
        std::string strin;
        std::getline(std::cin, strin);

        if (!strin.compare("SendFile"))
        {
            std::cerr << "enter a filepath" << std::endl;
            std::cin >> strin;

            this->addFileToSend(strin);
        }
        else
        {
            this->sendMsg(std::string(strin));
        }
    }
    return 0;
}


void *
Connection::thread_recv(void *arg)
{
    std::cerr << "=====================" << std::endl
              << "new thread: " << pthread_self().p << std::endl
              << "=====================" << std::endl;
    Connection *pCon = (Connection *)arg;

//    pthread_mutex_lock(&pCon->m_mutex);

    pCon->waitForRecv(*pCon);
    pthread_exit(NULL);

//    pthread_mutex_unlock(&pCon->m_mutex);

    return (void *)0;
}

void *
Connection::thread_sendfile(void *arg)
{

    Connection *pCon = (Connection *)arg;
    std::map<int, FileStatus>::iterator mapIt;
    FileStatus *pStatus;
    FileCon filecon;
    ConInfo conInfo;
    conInfo.type = CI_FILECONTENT;
    int readcount;
    std::cerr <<  "send map size:" << pCon->m_FilesSend.size() << std::endl;

    while (!pCon->m_FilesSend.empty())
    {
        for (mapIt = pCon->m_FilesSend.begin();
                mapIt != pCon->m_FilesSend.end();
            )
        {

            if (mapIt->second.status != FS_FILE_SENDING)
            {

//                std::cerr << mapIt->second.status << std::endl;
                continue;
            }

            filecon.key = mapIt->first;
            pStatus = &mapIt->second;
            if (feof(pStatus->pFILE))
            {
                conInfo.type = CI_FILECLOSE;
                fclose(pStatus->pFILE);
                pCon->m_FilesSend.erase(mapIt++);

                std::cerr << "File send end" << std::endl;

            }
            else
            {

                conInfo.type = CI_FILECONTENT;
                readcount = fread(filecon.content, sizeof(filecon.content[0]),
                                   sizeof(filecon.content), pStatus->pFILE);
                filecon.len = readcount;
                std::cerr << "read len:" << readcount << std::endl;
                pStatus->curSize += readcount;
                int nProgress;
                if (pStatus->curSize == pStatus->fileInfo.filesize)
                {
                    nProgress = 100;
                }
                else
                {
                    nProgress = (int) ((double)pStatus->curSize / pStatus->fileInfo.filesize * 100);
                }
                send_file(std::string(pStatus->fileInfo.pathname) + "\\" + pStatus->fileInfo.filename,
                          nProgress);
                mapIt++;
            }

            memcpy(conInfo.content, &filecon, sizeof(filecon));
            if (conInfo.type == CI_MSG)
            {
                std::cerr << "MSG DEBUG" <<std::endl;
            }
            send(pCon->m_sendfd, (char *) &conInfo, sizeof(conInfo), 0);
            std::cerr << "send block" << std::endl;
        }
        /*all file has sent, thread function exit
        now we reset m_sendfilePid*/ // Miles modified
        pCon->m_sendfilePid.p = NULL;
        std::cerr << "thread sendfile exit" << std::endl;
    }
    return NULL;
}

int
Connection::addFileToRecv(ConInfo & recvConInfo, FileInfo **pFileInfo)
{
    FileInfo *pFile = (FileInfo *) recvConInfo.content;
    *pFileInfo = pFile;
    std::cerr << pFile->filename << std::endl;
    std::cerr << pFile->pathname << std::endl;
    std::cerr << pFile->filesize << std::endl;
    std::cerr << pFile->fileKey << std::endl;
    std::cerr << pFile->md5 << std::endl;


    if (std::cin.good())
    {
        std::cerr << "cin is good" << std::endl;
    }
    getPath(pFile->pathname, sizeof(pFile->pathname));
    std::string filepath(pFile->pathname);
    filepath += "\\";
    filepath += pFile->filename;

    std::cerr << "filepath " << filepath << std::endl;
    if (access(filepath.c_str(), F_OK) == 0)
    {
        std::cerr << "File : " << filepath << "  is existed" << std::endl;
        if (access(filepath.c_str(), W_OK) == -1)
        {
            std::cerr << "File : " << filepath << "  can't write" << std::endl;
            return -1;
        }
    }

    FILE *fout = fopen(filepath.c_str(), "wb+");

    FileStatus status = {0};
    status.pFILE = fout;

    status.fileInfo.fileKey = pFile->fileKey;
    status.fileInfo.filesize = pFile->filesize;
    memcpy(status.fileInfo.md5, pFile->md5, sizeof(pFile->md5));

    std::cerr << "md5 len :" << status.fileInfo.md5 << std::endl;
    splitFileName(filepath, &status.fileInfo);
    status.direction = FD_FILE_RECV;
    status.status = FS_FILE_WAITFORSEND;
    status.curSize = 0;

    FileCon fC;
    fC.key = pFile->fileKey;
    fC.len = 0;

    ConInfo con;
    memcpy(con.content, (char *) &fC, sizeof(fC.key));

    if (!fout)
    {
        std::cerr << "File Open error" << std::endl;
        status.status = FS_FILE_CANCEL;
        con.type = CI_FILESENDCANCEL;
        send(this->m_sendfd, (char *) &con, sizeof(con), 0);
        return -1;
    }
    else
    {
        con.type = CI_FILESTARTSEND;
        send(this->m_sendfd, (char *) &con, sizeof(con), 0);
    }

    m_FilesRecv.insert(std::pair< int, FileStatus>(pFile->fileKey, status));

    return 0;
}

int
Connection::getFileKey()
{
    if (m_FilesSend.empty())
    {
        return 1;
    }
    else
    {
        return m_FilesSend.rbegin()->first + 1;
    }

}

int
Connection::addFileToSend(const std::string &filePath)
{
    if (this->m_sendfd == 0)
    {
        startCon();
    }

    if (this->m_sendfd == 0)
    {
        perror("sendfd is not ready");
        return -1;
    }

    FileStatus status;
    status.direction = FD_FILE_SEND;


    FILE *fin = fopen(filePath.c_str(), "rb");

    if (fin == NULL)
    {
        std::cerr << "File open error!" << std::endl;
        return -1;
    }

    if (access(filePath.c_str(), R_OK) == -1)
    {
        std::cerr << "File can't access to send" << std::endl;
        return -2;
    }

    int key = getFileKey();

    status.pFILE = fin;

    FileInfo fileInfo;
    fileInfo.filesize = filelength(fileno(fin));
    fileInfo.fileKey = key;

    std::cerr << "key:" << key << std::endl;

    splitFileName(filePath, &fileInfo);

    status.fileInfo = fileInfo;
    status.status = FS_FILE_WAIT;
    status.curSize = 0;

    fseek(fin, 0L, SEEK_SET);
    fclose(fin);
    std::ifstream stream(filePath.c_str(), std::ios::binary);
    md5 fileMd5(stream);
    std::string str_md5 = fileMd5.md5_get_hash();
    memcpy(fileInfo.md5, str_md5.c_str(), str_md5.size());
    fileInfo.md5[str_md5.size()] = '\0';

    stream.close();

    fin = fopen(filePath.c_str(), "rb");
    ConInfo fileCon;
    fileCon.type = CI_FILENEW;
    memcpy(&fileCon.content, &fileInfo, sizeof(fileInfo));

    m_FilesSend.insert( std::pair <int, FileStatus> (key, status));
    send(m_sendfd, (char *) &fileCon, sizeof(fileCon), 0);

    ///insert first!!!!! damn it!
//    m_FilesSend.insert( std::pair <int, FileStatus> (key, status));
    std::cerr << "ins & thread: "  << m_FilesSend.size() << std::endl;

    if (m_sendfilePid.p == NULL)
    {
        pthread_create(&this->m_sendfilePid, NULL, this->thread_sendfile, this);
    }

    send_file_init(this->m_conaddr,
                   filePath,
                   fileInfo);
    return 0;
}

void
Connection::setRecvfd(int newRecvfd)
{
    m_sendfd = m_recvfd = newRecvfd;
    pthread_create(&m_recvPid, NULL, this->thread_recv, this);

    return ;
}
